#include "tea.h"

QByteArray TEA::plain = QByteArray(8,0);
QByteArray TEA::prePlain = QByteArray(8,0);
long TEA::mKey[4];
QByteArray TEA::output;
int TEA::indexCrypt;
int TEA::preIndexCrypt;
int TEA::pos;
int TEA::padding;
bool TEA::header;
int TEA::contextStart;

void TEA::setKey(const QByteArray &key)
{
    if(key.size() < 16)abort();//the key.length is wrong.
    for(int i=0;i<16;i+=4)mKey[i/4] = unpack(key,i,4);
}

void TEA::setKey(const long key[])
{
    memcpy(mKey,key,sizeof(long)*4);
}

QByteArray TEA::encode(const QByteArray &data)
{
    long l = unpack(data,0,4);
    long r = unpack(data,4,4);
    long sum = 0;
    long delta = 0x9E3779B9L;
    for(int i=0;i<16;++i){
        sum = sum + delta & 0xFFFFFFFFL;
        l = l + ((r << 4) + mKey[0] ^ r + sum ^ ((ulong)r >> 5) + mKey[1]) & 0xFFFFFFFFL;
        r = r + ((l << 4) + mKey[2] ^ l + sum ^ ((ulong)l >> 5) + mKey[3]) & 0xFFFFFFFFL;
    }
    return twoIntToBytes((int)l,(int)r);
}

QByteArray TEA::decode(const QByteArray &data, const uint offset)
{
    long l = unpack(data, offset, 4);
    long r = unpack(data, offset + 4, 4);
    long delta1 = 0xE3779B90L;
    long delta2 = 0x9E3779B9L;
    for(int i=0;i<16;++i){
        r = r - ((l << 4) + mKey[2] ^ l + delta1 ^ ((ulong)l >> 5) + mKey[3]) & 0xFFFFFFFFL;
        l = l - ((r << 4) + mKey[0] ^ r + delta1 ^ ((ulong)r >> 5) + mKey[1]) & 0xFFFFFFFFL;
        delta1 = delta1 - delta2 & 0xFFFFFFFFL;
    }
    return twoIntToBytes((int)l,(int)r);
}

void TEA::encodeOneChunk()
{
    pos = 0;
    while(pos < 8) {
        plain[pos] = header ? ((char)(plain[pos] ^ prePlain[pos])) : ((char)(plain[pos] ^ output[preIndexCrypt + pos]));
        ++pos;
    }

    output.replace(indexCrypt,8,encode(plain));
    pos = 0;
    while(pos < 8) {
        int v1 = indexCrypt + pos;
        output[v1] = ((char)(output[v1] ^ prePlain[pos]));
        ++pos;
    }

    prePlain = plain;
    preIndexCrypt = indexCrypt;
    indexCrypt += 8;
    pos = 0;
    header = false;
}

bool TEA::decodeOneChunk(const QByteArray &data, const uint offset, const uint size)
{
    bool ret = true;
    pos = 0;
    while(true) {
        if(pos >= 8) {
            break;
        }
        else if(contextStart + pos < size) {
            prePlain[pos] = ((char)(prePlain[pos] ^ data[indexCrypt + offset + pos]));
            ++pos;
            continue;
        }

        return ret;
    }

    prePlain = decode(prePlain);
    if(false) {//prePlain == null
        ret = false;
    }
    else {
        contextStart += 8;
        indexCrypt += 8;
        pos = 0;
    }

    return ret;
}

QByteArray TEA::encrypt(const QByteArray &data, const uint offset, const uint size)
{
    int tmpOffset;
    int i;

    pos = 1;
    padding = preIndexCrypt = indexCrypt = 0;
    header = true;
    pos = (size + 10) % 8;
    if(pos != 0) pos = 8 - pos;
    output.clear();
    output.resize(pos + size + 10);
    plain[0] = ((char)(qrand() & 0xF8 | pos));
    for(i = 1; i <= pos; ++i)plain[i] = ((char)(qrand() & 0xFF));
    ++pos;
    for(i = 0; i < 8; ++i)prePlain[i] = 0;
    padding = 1;

    while(padding <= 2) {
        if(pos < 8) {
            tmpOffset = pos;
            pos = tmpOffset + 1;
            plain[tmpOffset] = ((char)(qrand() & 0xFF));
            ++padding;
        }
        if(pos != 8)continue;
        encodeOneChunk();
    }

    tmpOffset = offset;
    int v3 = size;
    while(v3 > 0) {
        if(pos < 8) {
            int v6 = pos;
            pos = v6 + 1;
            i = tmpOffset + 1;
            plain[v6] = data[tmpOffset];
            tmpOffset = v3 - 1;
        }
        else {
            i = tmpOffset;
            tmpOffset = v3;
        }

        if(pos == 8) {
            encodeOneChunk();
            v3 = tmpOffset;
            tmpOffset = i;
            continue;
        }

        v3 = tmpOffset;
        tmpOffset = i;
    }

    padding = 1;
    while(padding <= 7) {
        if(pos < 8) {
            int v1 = pos;
            pos = v1 + 1;
            plain[v1] = 0;
            ++padding;
        }

        if(pos != 8) {
            continue;
        }

        encodeOneChunk();
    }

    return output;
}

QByteArray TEA::encrypt(const QByteArray &data)
{
    return encrypt(data,0,data.size());
}

QByteArray TEA::encrypt(const QByteArray &data, const QByteArray &key)
{
    setKey(key);
    return encrypt(data);
}

QByteArray TEA::decrypt(const QByteArray &data, const uint offset, const uint size)
{
    QByteArray ret;
    preIndexCrypt = 0;
    indexCrypt = 0;
    QByteArray tmp = QByteArray(offset + 8,0);
    if(size % 8 != 0 || size < 16) {
        ret = nullptr;
    }
    else {
        prePlain = decode(data, offset);
        pos = prePlain[0] & 7;
        int v4 = size - pos - 10;
        if(v4 < 0) {
            ret = nullptr;
        }
        else {
            int v0_1;
            for(v0_1 = offset; v0_1 < tmp.size(); ++v0_1) {
                tmp[v0_1] = 0;
            }

            output.resize(v4);
            preIndexCrypt = 0;
            indexCrypt = 8;
            contextStart = 8;
            ++pos;
            padding = 1;
            for(ret = tmp; padding <= 2; ret = data) {
                if(pos < 8) {
                    ++pos;
                    ++padding;
                }

                if(pos != 8) {
                    continue;
                }

                if(!decodeOneChunk(data, offset, size)) {
                    return nullptr;
                }
            }

            int v1_1 = v4;
            v0_1 = 0;
            QByteArray v2;
            for(v2 = ret; v1_1 != 0; v2 = data) {
                if(pos < 8) {
                    output[v0_1] = ((char)(v2[preIndexCrypt + offset + pos] ^ prePlain[pos]));
                    ++v0_1;
                    --v1_1;
                    ++pos;
                }

                if(pos != 8) {
                    continue;
                }

                preIndexCrypt = indexCrypt - 8;
                if(!decodeOneChunk(data, offset, size)) {
                    return nullptr;
                }
            }

            padding = 1;
            ret = v2;
            while(padding < 8) {
                if(pos < 8) {
                    if((ret[preIndexCrypt + offset + pos] ^ prePlain[pos]) != 0) {
                        return nullptr;
                    }
                    else {
                        ++pos;
                    }
                }

                if(pos == 8) {
                    preIndexCrypt = indexCrypt;
                    if(!decodeOneChunk(data, offset, size)) {
                        return nullptr;
                    }
                    else {
                        ret = data;
                    }
                }

                ++padding;
            }

            ret = output;
        }
    }

    return ret;
}

QByteArray TEA::decrypt(const QByteArray &data)
{
    return decrypt(data,0,data.size());
}

QByteArray TEA::decrypt(const QByteArray &data, const QByteArray &key)
{
    setKey(key);
    return decrypt(data);
}

long TEA::unpack(const QByteArray &data, uint offset, const uint size)
{
    long v2 = 0;
    int v0 = size > 4 ? offset + 4 : offset + size;
    while(offset < v0) {
        v2 = v2 << 8 | (((long)(data[offset] & 0xFF)));
        ++offset;
    }

    return 0xFFFFFFFFL & v2;
}

QByteArray TEA::twoIntToBytes(int a, int b)
{
    QByteArray ret;
    ret.resize(8);
    for(int j=3;j>=0;--j)ret[3-j] =  (uint)a >> (j*8);
    for(int j=3;j>=0;--j)ret[7-j] =  (uint)b >> (j*8);
    return ret;
}

